using System;
using System.Collections;
using System.Collections.Generic;
using System.ComponentModel;
using System.Data;
using System.Drawing;
using System.IO;
using System.Media;
using System.Windows.Forms;
using System.Windows.Forms.DataVisualization.Charting;
using CubeTimer.Properties;
using CubeTimer.Scramble;
using CubeTimer.Storage;

namespace CubeTimer
{
  public partial class CubeTimerForm : Form
  {
    private const string c_mConStr = "Data Source=iRubix.db3;Pooling=true;FailIfMissing=false";
    readonly CubeTimesDB m_db = new CubeTimesDB(c_mConStr);
    private int m_seconds2CountDown;
    private DateTime m_startTime;
    private TimeSpan m_diffTime;
    private TimerState m_state;
    private bool m_initComboBoxes;
    private List<CubeTimeRow> m_selectedTimes;
    private bool m_sortedByTimeAsc;
    private bool m_updateDates;

    public CubeTimerForm()
    {
      InitializeComponent();
      _Init();
      _filterByDay.DateWasSelected += SelectedDateChanged;
    }

    #region Event handlers

    private void _Init()
    {
      m_initComboBoxes = true;
      _cbCountDown.SelectedIndex = 1;
      _cbCubeType.SelectedIndex = 1;
      m_db.CubeType = _cbCubeType.Text;
      _cbChartType.SelectedIndex = 1;
      m_initComboBoxes = false;
      m_state = TimerState.None;
      _lblTimer.Text = string.Empty;
    }

    private void CubeTimerForm_Load(object sender, EventArgs e)
    {
      m_db.Open();
      RepopulateData();
      UpdateCountdown();
      _btnStartStop.Focus();
    }

    private void CubeTimerForm_FormClosing(object sender, FormClosingEventArgs e)
    {
      m_db.Close();
    }

    private void StartStop_Click(object sender, EventArgs e)
    {
      switch (m_state)
      {
        case TimerState.None:
          StartTimer();
          _lblTimer.Visible = true;
          _btnStartStop.Text = "Stop";
          m_state = TimerState.Started;
          break;
        case TimerState.Started:
          m_Timer.Stop();
          if (m_seconds2CountDown > 0)
          {
            Discard_Click(null, EventArgs.Empty);
          }
          else
          {
            _btnStartStop.Text = "Accept";
            _btnDiscard.Visible = true;
            _btnDNF.Visible = true;
            _btnPlus2Seconds.Visible = true;
            m_state = TimerState.Stopped;
          }
          break;
        case TimerState.Stopped:
          Accept();
          SetAcceptedStatus();
          break;
        case TimerState.Accepted:
          StartTimer();
          _lblTimer.Visible = true;
          _btnStartStop.Text = "Stop";
          m_state = TimerState.Started;
          break;
      }
    }

    private void OnTimerTick(object sender, EventArgs e)
    {
      if (m_seconds2CountDown > 0)
      {
        if (m_seconds2CountDown == 0)
          UpdateTimerLabel();
        UpdateCoolDownLabel();
      }
      else
      {
        UpdateTimerLabel();
      }
    }

    private void Discard_Click(object sender, EventArgs e)
    {
      SetAcceptedStatus();
    }

    private void BtnPlus2Seconds_Click(object sender, EventArgs e)
    {
      m_diffTime = m_diffTime.Add(TimeSpan.FromSeconds(2));
      Accept();
      SetAcceptedStatus();
    }

    private void SetAcceptedStatus()
    {
      _lblTimer.Visible = false;
      _btnDiscard.Visible = false;
      _btnDNF.Visible = false;
      _btnPlus2Seconds.Visible = false;
      _btnStartStop.Text = "Start";
      m_state = TimerState.Accepted;
      _btnStartStop.Focus();
    }

    private void BtnDNF_Click(object sender, EventArgs e)
    {
      m_diffTime = TimeSpan.MaxValue;
      Accept();
      SetAcceptedStatus();
    }

    private void CDSelectedIndexChanged(object sender, EventArgs e)
    {
      if (m_initComboBoxes) return;
      UpdateCountdown();
    }

    private void CubeType_SelectedIndexChanged(object sender, EventArgs e)
    {
      lblScramble.Text = GenerateScramble();
      if (m_initComboBoxes) return;
      m_db.CubeType = _cbCubeType.Text;
      RepopulateData();
    }

    private void ChartType_SelectedIndexChanged(object sender, EventArgs e)
    {
      if (m_initComboBoxes) return;
      m_chart.Series[0].ChartType = (SeriesChartType)Enum.Parse(typeof(SeriesChartType), _cbChartType.Text, true);
    }

    private void ResetStats_Click(object sender, EventArgs e)
    {
      m_db.DropTable(true);
      m_db.CreateTable();
    }

    private void Chart_MouseClick(object sender, MouseEventArgs e)
    {
      HitTestResult result = m_chart.HitTest(e.X, e.Y);
      if (result.Series != null && e.Button == MouseButtons.Left)
      {
        DataRow row = ((DataTable)m_chart.DataSource).Rows[result.PointIndex];
        m_selectedTimes = (List<CubeTimeRow>)row["Values"];
        var date = (DateTime)row["When"];
        SelectedDateChanged(this, new DateSelectedEvetArgs(date));
        _filterByDay.SetDate(date);
        _tabPageControl.SelectedIndex = 1;
      }
    }

    private void Chart_GetToolTipText(object sender, ToolTipEventArgs e)
    {
      HitTestResult result = e.HitTestResult;
      if (result.Series != null)
      {
        DataRow row = ((DataTable)m_chart.DataSource).Rows[result.PointIndex];
        List<CubeTimeRow> cubeTimes = (List<CubeTimeRow>)row["Values"];
        TimeSpan average = CubeTimesDB.GetAverage(cubeTimes, cubeTimes.Count);
        TimeSpan best = CubeTimesDB.GetBestTime(cubeTimes);
        DateTime when = (DateTime)row["When"];
        e.Text = when.Date.ToShortDateString();
        e.Text += Environment.NewLine + string.Format("Average of {0} is {1}.", cubeTimes.Count, TimeHelper.GetTimeMilliseconds(average));
        e.Text += Environment.NewLine + string.Format("Best is {0}.", TimeHelper.GetTimeMilliseconds(best));
      }
    }

    private void OnGrid_KeyUp(object sender, KeyEventArgs e)
    {
      int firstSelectedIndex = -1;
      int columnIndex = -1;
      if (e.KeyCode == Keys.Delete)
      {
        foreach (DataGridViewRow selectedRow in _grid.SelectedRows)
        {
          firstSelectedIndex = selectedRow.Index;
          columnIndex = _grid.CurrentCell.ColumnIndex;
          m_db.RemoveTime(((CubeTimeRow)selectedRow.DataBoundItem).CubeTime);
        }
        RepopulateData();
        if (firstSelectedIndex > 0 && firstSelectedIndex < _grid.Rows.Count && columnIndex > 0)
          _grid.CurrentCell = _grid.Rows[firstSelectedIndex].Cells[columnIndex];
      }
    }

    private void SelectedDateChanged(object sender, DateSelectedEvetArgs e)
    {
      m_selectedTimes = m_db.GetTimesExactly(e.Date);
      _lblSelectedDate.Text = string.Format(_lblSelectedDate.Tag.ToString(), e.Date.ToShortDateString(), m_selectedTimes.Count);
      m_updateDates = true;
      RepopulateData();
      m_updateDates = false;
    }

    #endregion

    #region Private methods

    private void RepopulateData()
    {
      try
      {
        if (m_selectedTimes == null) 
          m_selectedTimes = m_db.GetTimes();
        List<CubeTimeRow> cubeTimes = m_selectedTimes;
        BindChartData(CubeTimesDB.GetChartData(m_db.GetTimes()));
        BindGridData(cubeTimes);
        ReadAverages();
        if (!m_updateDates)
          _filterByDay.Init(m_db.GetDays());
      }
      catch (Exception ex)
      {
        MessageBox.Show(string.Format("Error happened.\n{0}", ex.Message));
        Close();
      }
    }

    private void BindGridData(List<CubeTimeRow> cubeTimes)
    {
      _grid.DataSource = null;
      List<CubeTimeRow> statsRows = cubeTimes;
      statsRows.Reverse();
      _grid.DataSource = statsRows;
      _grid.Columns[0].Visible = false;
      _grid.Columns[1].SortMode = DataGridViewColumnSortMode.Automatic;
      _grid.Columns[2].SortMode = DataGridViewColumnSortMode.Automatic;
      _grid.Columns[3].SortMode = DataGridViewColumnSortMode.NotSortable;
      _grid.Columns[4].SortMode = DataGridViewColumnSortMode.NotSortable;
      _grid.AutoResizeColumns();
    }

    private void BindChartData(DataTable cubeTimes)
    {
      m_chart.DataSource = cubeTimes;
      m_chart.Series[0].XValueMember = "When";
      m_chart.Series[0].YValueMembers = "Time";
      m_chart.DataBind();
    }

    private void ReadAverages()
    {
      lblLastTime.Text = TimeHelper.GetTimeMilliseconds(m_db.GetLastTimesAvg(1));
      lblAverage3Times.Text = TimeHelper.GetTimeMilliseconds(m_db.GetLastTimesAvg(3));
      lblAverage5Times.Text = TimeHelper.GetTimeMilliseconds(m_db.GetLastTimesAvg(5));
      lblAverage10Times.Text = TimeHelper.GetTimeMilliseconds(m_db.GetLastTimesAvg(10));
      lblAverage15Times.Text = TimeHelper.GetTimeMilliseconds(m_db.GetLastTimesAvg(15));
      lblAverage30Times.Text = TimeHelper.GetTimeMilliseconds(m_db.GetLastTimesAvg(30));

      lblTodayBest.Text = TimeHelper.GetTimeMilliseconds(m_db.GetBestTime(DateTime.Now));
      lblTodayAverage.Text = TimeHelper.GetTimeMilliseconds(m_db.GetTimesAvg(DateTime.Now));
      _lblTodayAverageTitle.Text = string.Format(_lblTodayAverageTitle.Tag.ToString(), m_db.GetTimesCount(DateTime.Now));
      DateTime minusWeek = DateTime.Now.Subtract(TimeSpan.FromDays(7));
      lblWeekBest.Text = TimeHelper.GetTimeMilliseconds(m_db.GetBestTime(minusWeek));
      lblWeekAverage.Text = TimeHelper.GetTimeMilliseconds(m_db.GetTimesAvg(minusWeek));
      lblTotalBestTime.Text = TimeHelper.GetTimeMilliseconds(m_db.GetTotalBestTime());
      lblAverageTime.Text = TimeHelper.GetTimeMilliseconds(m_db.GetTimesAvg());
      _lblCubesSummary.Text = string.Format("{0}", m_db.GetNumOfSolves());
      _lblCubesSummary.Text += string.Format("            This week: {0}", m_db.GetNumOfSolves(minusWeek));
      _lblCubesSummary.Text += string.Format("            Per day: {0}", m_db.GetPerOfSolves());
    }

    private string GenerateScramble()
    {
      Scramble.Scramble scramble = null;
      switch (_cbCubeType.SelectedIndex)
      {
        case 1:
          scramble = new Scramble3x3(25);
          break;
        case 2:
          scramble = new Scramble4x4(30);
          break;
      }
      return scramble == null ? "N/A" : scramble.GenerateScramble();
    }

    private void StartTimer()
    {
      m_startTime = DateTime.Now;
      UpdateCountdown();
      if (m_seconds2CountDown > 0)
      {
        m_Timer.Interval = 1000;
        _lblTimer.ForeColor = Color.FromName("HotTrack");
        _lblTimer.Text = string.Format("{0}", m_seconds2CountDown < 0 ? 0 : m_seconds2CountDown);
      }
      else
      {
        m_Timer.Interval = 100;
      }
      m_Timer.Start();
    }

    private void UpdateTimerLabel()
    {
      if (m_Timer.Interval == 1000) m_Timer.Interval = 100;
      m_diffTime = DateTime.Now - m_startTime;
      _lblTimer.ForeColor = Color.FromName("HotTrack");
      _lblTimer.Text = TimeHelper.GetTimeMilliseconds(m_diffTime);
    }

    private void UpdateCoolDownLabel()
    {
      m_diffTime = DateTime.Now - m_startTime;
      m_seconds2CountDown--;
      if (m_seconds2CountDown > 3)
      {
        _lblTimer.ForeColor = Color.FromName("HotTrack");
      }
      else if (m_seconds2CountDown != 0)
      {
        PlayAudio(Resources.blip);
        _lblTimer.ForeColor = Color.FromName("Maroon");
      }
      else
      {
        PlayAudio(Resources.ready);
      }

      _lblTimer.Text = string.Format("{0}", m_seconds2CountDown < 0 ? 0 : m_seconds2CountDown);
      m_startTime = DateTime.Now;
    }

    private void PlayAudio(Stream stream)
    {
      try
      {
        if (!_checkSound.Checked) return;
        SoundPlayer soundPlayer = new SoundPlayer(stream);
        soundPlayer.Play();
      }
      catch
      {
        return;
      }
    }

    private void Accept()
    {
      CubeTime cubeTime = new CubeTime(DateTime.Now, m_diffTime, _cbCubeType.Text, lblScramble.Text);
      try
      {
        m_db.AddTime(cubeTime);
      }
      catch (Exception ex)
      {
        MessageBox.Show(ex.Message);
      }
      lblScramble.Text = GenerateScramble();
      RepopulateData();
    }

    private void UpdateCountdown()
    {
      int count;
      int.TryParse(_cbCountDown.Text, out count);
      m_seconds2CountDown = count;
    }

    #endregion

    private void _grid_ColumnHeaderMouseClick(object sender, DataGridViewCellMouseEventArgs e)
    {
      bool prevSort = m_sortedByTimeAsc;
      m_sortedByTimeAsc = !m_sortedByTimeAsc;
      if (!prevSort)
        m_selectedTimes.Sort((x, y) => x.CubeTime.ElapsedTicks.CompareTo(y.CubeTime.ElapsedTicks));
      else
        m_selectedTimes.Sort((x, y) => y.CubeTime.ElapsedTicks.CompareTo(x.CubeTime.ElapsedTicks));
      BindGridData(m_selectedTimes);
    }
  }
}